package ru.spb.osll.myfit.server.execute;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.thoughtworks.xstream.XStream;
import org.apache.log4j.Logger;
import ru.spb.osll.myfit.client.service.SuiteExecutingService;
import ru.spb.osll.myfit.common.*;
import ru.spb.osll.myfit.server.Configurator;
import ru.spb.osll.myfit.server.parser.PageUtils;
import ru.spb.osll.myfit.server.parser.SuiteHandler;
import ru.spb.osll.myfit.server.services.SuiteServiceImpl;
import ru.spb.osll.myfit.util.FileHelper;
import ru.spb.osll.myfit.util.StringUtils;

import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

import static ru.spb.osll.myfit.server.SuiteUtils.getSuitePathFromId;

public class SuiteExecutingServiceImpl extends RemoteServiceServlet implements SuiteExecutingService {
  private static Logger m_logger = Logger.getLogger(SuiteServiceImpl.class);
  private static final long serialVersionUID = -8601084862651052691L;
  private final PageExecutingServiceImpl m_pageExecutingService = new PageExecutingServiceImpl();
  private SuiteHandler m_suiteHandler = new SuiteHandler();
  private Configurator m_configurator = Configurator.getInstance();
  private FileHelper m_helper = new FileHelper();
  private final Map<String, SuiteExecutor> m_executors = new HashMap<String, SuiteExecutor>();

  public SuiteExecutingServiceImpl() {
    m_logger.info("SuiteExecutingServiceImpl constructor");
  }

  @Override
  public SuiteExecutingResult executeSuite(String suiteId, Map<String, String> metadata) {
    if (!rootPageExists(suiteId)) {
      return null;
    }

    String suiteResultDir = m_helper.createUDir(m_configurator.getSuiteResultsDirectory(), suiteId);
    String suiteResultId = suiteResultDir.substring(suiteResultDir.lastIndexOf(File.separator) + 1);

    String date = StringUtils.substringFromPattern(FileHelper.FORMAT_PATTERN, suiteResultId, 2);
    Date startTime = null;
    try {
      startTime = StringUtils.dateFromString(date, FileHelper.DATE_FORMAT);
    } catch (ParseException e) {
      m_logger.error("Error parsing date needed for suite executor.");
    }

    saveMetadata(suiteId, suiteResultId, startTime, metadata);

    SuiteExecutor suiteExecutor = new SuiteExecutor(suiteResultId, m_suiteHandler, startTime, m_pageExecutingService, m_helper);

    suiteExecutor.setMetadata(metadata);

    suiteExecutor.start();

    SuiteExecutingResult result = suiteExecutor.getSuiteExecutingResult();

    if (result.getState() != ExecutingResult.ExecutingState.FINISH) {
      synchronized (m_executors) {
        m_executors.put(suiteExecutor.getExecutorId(), suiteExecutor);
      }
    }

    return result;
  }

  private boolean rootPageExists(String suiteId) {
    Suite suite = null;

    String suiteFilePath = getSuitePathFromId(suiteId);

    try {
      suite = m_suiteHandler.getSuite(suiteFilePath);
    } catch (FileNotFoundException e) {
      m_logger.error(e.getMessage() + "\n" + e.getStackTrace());
      return false;
    } catch (ParserConfigurationException e) {
      m_logger.error(e.getMessage() + "\n" + e.getStackTrace());
      return false;
    }

    suite.setId(suiteId);

    File testRootDirectory = new File(PageUtils.getFullPath(m_configurator.getRootDir(), suite.getRoot()));
    return testRootDirectory.exists();
  }

  private void saveMetadata(String suiteId, String suiteResultId, Date startTime, Map<String, String> metadata) {
    metadata.put(TestResultMetadataConstants.START_TIME, new SimpleDateFormat(TestResultMetadataConstants.METADATA_DATE_FORMAT, Locale.ENGLISH).format(startTime));
    TestResultMetadataDao dao = new TestResultMetadataDao();
    String metadataFilePath = m_configurator.getSuiteResultsDirectory()
            + File.separator + suiteResultId + File.separator + suiteId + ".metadata";
    OutputStream stream = null;
    try {
      stream = m_helper.openFile(metadataFilePath);
      dao.save(metadata, stream);
    } catch(IOException e) {
    } finally {
      m_helper.close(stream);
    }
  }

  private Map<String, String> loadMetadata(String filename) {
    TestResultMetadataDao dao = new TestResultMetadataDao();
    InputStream stream = null;
    try {
      stream = m_helper.openInputFile(filename);
      return dao.load(stream);
    } catch(IOException e) {
    } finally {
      m_helper.close(stream);
    }
    return new TreeMap<String, String>();
  }

  @Override
  public SuiteExecutingResult getExecutingResult(String id) {
    SuiteExecutor executor;
    synchronized (m_executors) {
      executor = m_executors.get(id);
    }

    if (executor == null)
      return getSuiteResultFromFs(id);

    SuiteExecutingResult result = executor.getSuiteExecutingResult();
    if (result.getState() == ExecutingResult.ExecutingState.FINISH) {
      synchronized (m_executors) {
        m_executors.remove(id);
      }
    }

    return result;
  }

  private SuiteExecutingResult getSuiteResultFromFs(String id) {
    String resultFileName = StringUtils.substringFromPattern(FileHelper.FORMAT_PATTERN, id, 1);
    String date = StringUtils.substringFromPattern(FileHelper.FORMAT_PATTERN, id, 2);
    Date time;
    SuiteExecutingResult result = null;
    try {
      time = StringUtils.dateFromString(date, FileHelper.DATE_FORMAT);

      XStream xStream = new XStream();
      xStream.processAnnotations(SuiteExecutingResult.class);

      File suiteResultFile = new File(m_configurator.getSuiteResultsDirectory()
              + File.separator + id + File.separator + resultFileName + ".xml");
      if (suiteResultFile.exists()) {
        result = (SuiteExecutingResult)xStream.fromXML(suiteResultFile);
        result.setName(resultFileName);
        result.setStartTime(time);

        Map<String, String> metadata = loadMetadata(m_configurator.getSuiteResultsDirectory() +
                File.separator +
                id +
                File.separator +
                resultFileName + ".metadata");
        result.setMetadata(metadata);
      }
    } catch (Exception e) {
      m_logger.error("Error reading suites results: " + e.getMessage());
    }
    return result;
  }

  @Override
  public Set<SuiteExecutingResult> getSuiteResults(String resultId) {
    m_logger.info("getSuiteResults(" + resultId + ")");
    Map<String, SuiteExecutingResult> map = new HashMap<String, SuiteExecutingResult>();
    Set<SuiteExecutingResult> results = new HashSet<SuiteExecutingResult>();
    getMapResults(map);
    getFsResults(map);

    Set<Map.Entry<String, SuiteExecutingResult>> set = map.entrySet();
    for (Map.Entry<String, SuiteExecutingResult> aSet : set) {
      SuiteExecutingResult result = aSet.getValue();
      if (resultId == null || result.getId().equals(resultId)) {
        results.add(result);
      }
    }

    return results;
  }

  private void getFsResults(Map<String,SuiteExecutingResult> map) {
    File suitesResultsDirectory = new File(m_configurator.getSuiteResultsDirectory());
    if (!suitesResultsDirectory.exists()) {
      return;
    }

    File[] resultsPaths = suitesResultsDirectory.listFiles(new FileFilter() {
      public boolean accept(File file) {
        return file.isDirectory();
      }
    });

    for (File resultFile : resultsPaths) {
      String suiteResultId = resultFile.getPath().substring(resultFile.getPath().lastIndexOf(File.separator) + 1);
      SuiteExecutingResult result = getSuiteResultFromFs(suiteResultId);
      if (result != null) {
        map.put(suiteResultId, result);
      }
    }
  }

  private void getMapResults(Map<String, SuiteExecutingResult> map) {
    Map<String, SuiteExecutor> temp;

    synchronized (m_executors) {
      temp = new HashMap<String, SuiteExecutor>(m_executors);
    }

    Set<Map.Entry<String, SuiteExecutor>> set = temp.entrySet();
    for (Map.Entry<String, SuiteExecutor> entry : set) {
      SuiteExecutor executor = entry.getValue();
      map.put(entry.getKey(), executor.getSuiteExecutingResult());
    }
  }

  @Override
  public Set<SuiteExecutingResult> getSuiteResultsHeaders() {
    Set<SuiteExecutingResult> results = getSuiteResults(null);
    Set<SuiteExecutingResult> newResults = new HashSet<SuiteExecutingResult>();
    for (SuiteExecutingResult result : results) {
      SuiteExecutingResult newResult = new SuiteExecutingResult(result.getCounts(), result.getId(), result.getState(), result.getName(), result.getStartTime());
      newResults.add(newResult);
    }
    return newResults;
  }

  @Override
  public List<PageExecutingResult> getPageExecutingResults(String suiteResultId) {
    SuiteExecutingResult suiteExecutingResult = getExecutingResult(suiteResultId);
    return m_pageExecutingService.getExecutingResultsBatch(suiteExecutingResult.getPageExecutingResults());
  }
}
